/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef SkUnicode_DEFINED
#define SkUnicode_DEFINED
#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkUTF.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#if !defined(SKUNICODE_IMPLEMENTATION)
#define SKUNICODE_IMPLEMENTATION 0
#endif

#if !defined(SKUNICODE_API)
#if defined(SKUNICODE_DLL)
#if defined(_MSC_VER)
#if SKUNICODE_IMPLEMENTATION
#define SKUNICODE_API __declspec(dllexport)
#else
#define SKUNICODE_API __declspec(dllimport)
#endif
#else
#define SKUNICODE_API __attribute__((visibility("default")))
#endif
#else
#define SKUNICODE_API
#endif
#endif

namespace sknonstd {
template <typename T> struct is_bitmask_enum;
}

class SKUNICODE_API SkBidiIterator {
public:
    typedef int32_t Position;
    typedef uint8_t Level;
    struct Region {
        Region(Position start, Position end, Level level) : start(start), end(end), level(level) {}
        Position start;
        Position end;
        Level level;
    };
    enum Direction {
        kLTR,
        kRTL,
    };
    virtual ~SkBidiIterator() = default;
    virtual Position getLength() = 0;
    virtual Level getLevelAt(Position) = 0;
};

class SKUNICODE_API SkBreakIterator {
public:
    typedef int32_t Position;
    typedef int32_t Status;
    virtual ~SkBreakIterator() = default;
    virtual Position first() = 0;
    virtual Position current() = 0;
    virtual Position next() = 0;
    virtual Status status() = 0;
    virtual bool isDone() = 0;
    virtual bool setText(const char utftext8[], int utf8Units) = 0;
    virtual bool setText(const char16_t utftext16[], int utf16Units) = 0;
};

class SKUNICODE_API SkUnicode {
public:
    enum CodeUnitFlags {
        kNoCodeUnitFlag = 0x00,
        kPartOfWhiteSpaceBreak = 0x01,
        kGraphemeStart = 0x02,
        kSoftLineBreakBefore = 0x04,
        kHardLineBreakBefore = 0x08,
        kPartOfIntraWordBreak = 0x10,
        kControl = 0x20,
        kTabulation = 0x40,
        kGlyphClusterStart = 0x80,
        kIdeographic = 0x100,
        kEmoji = 0x200,
        kWordBreak = 0x400,
        kSentenceBreak = 0x800,
    };
    enum class TextDirection {
        kLTR,
        kRTL,
    };
    typedef size_t Position;
    typedef uint8_t BidiLevel;
    struct BidiRegion {
        BidiRegion(Position start, Position end, BidiLevel level) : start(start), end(end), level(level) {}
        Position start;
        Position end;
        BidiLevel level;
    };
    enum class LineBreakType {
        kSoftLineBreak = 0,
        kHardLineBreak = 100,
    };

    enum class BreakType { kWords, kGraphemes, kLines, kSentences };
    struct LineBreakBefore {
        LineBreakBefore(Position pos, LineBreakType breakType) : pos(pos), breakType(breakType) {}
        Position pos;
        LineBreakType breakType;
    };

    virtual ~SkUnicode() = default;

    virtual SkString toUpper(const SkString &) = 0;

    virtual bool isControl(SkUnichar utf8) = 0;
    virtual bool isWhitespace(SkUnichar utf8) = 0;
    virtual bool isSpace(SkUnichar utf8) = 0;
    virtual bool isTabulation(SkUnichar utf8) = 0;
    virtual bool isHardBreak(SkUnichar utf8) = 0;
    /* *
     * Returns if a code point may start an emoji sequence.
     * Returns true for '#', '*', and '0'-'9' since they may start an emoji sequence.
     * To determine if a list of code points begins with an emoji sequence, use
     * getEmojiSequence.
     *   */
    virtual bool isEmoji(SkUnichar utf8) = 0;
    virtual bool isEmojiComponent(SkUnichar utf8) = 0;
    virtual bool isEmojiModifierBase(SkUnichar utf8) = 0;
    virtual bool isEmojiModifier(SkUnichar utf8) = 0;
    virtual bool isRegionalIndicator(SkUnichar utf8) = 0;
    virtual bool isIdeographic(SkUnichar utf8) = 0;

    // Methods used in SkShaper and SkText
    virtual std::unique_ptr<SkBidiIterator> makeBidiIterator(const uint16_t text[], int count,
        SkBidiIterator::Direction) = 0;
    virtual std::unique_ptr<SkBidiIterator> makeBidiIterator(const char text[], int count,
        SkBidiIterator::Direction) = 0;
    virtual std::unique_ptr<SkBreakIterator> makeBreakIterator(const char locale[], BreakType breakType) = 0;
    virtual std::unique_ptr<SkBreakIterator> makeBreakIterator(BreakType type) = 0;

    // Methods used in SkParagraph
    static bool hasTabulationFlag(SkUnicode::CodeUnitFlags flags);
    static bool hasHardLineBreakFlag(SkUnicode::CodeUnitFlags flags);
    static bool hasSoftLineBreakFlag(SkUnicode::CodeUnitFlags flags);
    static bool hasGraphemeStartFlag(SkUnicode::CodeUnitFlags flags);
    static bool hasControlFlag(SkUnicode::CodeUnitFlags flags);
    static bool hasPartOfWhiteSpaceBreakFlag(SkUnicode::CodeUnitFlags flags);

    static bool extractBidi(const char utf8[], int utf8Units, TextDirection dir, std::vector<BidiRegion> *bidiRegions);
    virtual bool getBidiRegions(const char utf8[], int utf8Units, TextDirection dir,
        std::vector<BidiRegion> *results) = 0;
    // Returns results in utf16
    virtual bool getWords(const char utf8[], int utf8Units, const char *locale, std::vector<Position> *results) = 0;
    virtual bool getUtf8Words(const char utf8[], int utf8Units, const char *locale, std::vector<Position> *results) = 0;
    virtual bool getSentences(const char utf8[], int utf8Units, const char *locale, std::vector<Position> *results) = 0;
    virtual bool computeCodeUnitFlags(char utf8[], int utf8Units, bool replaceTabs,
        skia_private::TArray<SkUnicode::CodeUnitFlags, true> *results) = 0;
    virtual bool computeCodeUnitFlags(char16_t utf16[], int utf16Units, bool replaceTabs,
        skia_private::TArray<SkUnicode::CodeUnitFlags, true> *results) = 0;

    static SkString convertUtf16ToUtf8(const char16_t *utf16, int utf16Units);
    static SkString convertUtf16ToUtf8(const std::u16string &utf16);
    static std::u16string convertUtf8ToUtf16(const char *utf8, int utf8Units);
    static std::u16string convertUtf8ToUtf16(const SkString &utf8);

    template <typename Appender8, typename Appender16>
    static bool extractUtfConversionMapping(SkSpan<const char> utf8, Appender8 &&appender8, Appender16 &&appender16)
    {
        size_t size8 = 0;
        size_t size16 = 0;
        auto ptr = utf8.begin();
        auto end = utf8.end();
        while (ptr < end) {
            size_t index = SkToSizeT(ptr - utf8.begin());
            SkUnichar u = SkUTF::NextUTF8(&ptr, end);

            // All UTF8 code units refer to the same codepoint
            size_t next = SkToSizeT(ptr - utf8.begin());
            for (auto i = index; i < next; ++i) {
                // fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
                appender16(size8);
                ++size16;
            }
            // SkASSERT(fUTF16IndexForUTF8Index.size() == next);
            SkASSERT(size16 == next);
            if (size16 != next) {
                return false;
            }

            // One or two UTF16 code units refer to the same codepoint
            uint16_t buffer[2];
            size_t count = SkUTF::ToUTF16(u, buffer);
            // fUTF8IndexForUTF16Index.emplace_back(index);
            appender8(index);
            ++size8;
            if (count > 1) {
                // fUTF8IndexForUTF16Index.emplace_back(index);
                appender8(index);
                ++size8;
            }
        }
        // fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
        appender16(size8);
        ++size16;
        // fUTF8IndexForUTF16Index.emplace_back(fText.size());
        appender8(utf8.size());
        ++size8;

        return true;
    }

    template <typename Callback> void forEachCodepoint(const char *utf8, int32_t utf8Units, Callback &&callback)
    {
        const char *current = utf8;
        const char *end = utf8 + utf8Units;
        while (current < end) {
            auto before = current - utf8;
            SkUnichar unichar = SkUTF::NextUTF8(&current, end);
            if (unichar < 0)
                unichar = 0xFFFD;
            auto after = current - utf8;
            uint16_t buffer[2];
            size_t count = SkUTF::ToUTF16(unichar, buffer);
            callback(unichar, before, after, count);
        }
    }

    template <typename Callback> void forEachCodepoint(const char16_t *utf16, int32_t utf16Units, Callback &&callback)
    {
        const char16_t *current = utf16;
        const char16_t *end = utf16 + utf16Units;
        while (current < end) {
            auto before = current - utf16;
            SkUnichar unichar = SkUTF::NextUTF16((const uint16_t **)&current, (const uint16_t *)end);
            auto after = current - utf16;
            callback(unichar, before, after);
        }
    }

    template <typename Callback>
    void forEachBidiRegion(const uint16_t utf16[], int utf16Units, SkBidiIterator::Direction dir, Callback &&callback)
    {
        auto iter = makeBidiIterator(utf16, utf16Units, dir);
        const uint16_t *start16 = utf16;
        const uint16_t *end16 = utf16 + utf16Units;
        SkBidiIterator::Level currentLevel = 0;

        SkBidiIterator::Position pos16 = 0;
        while (pos16 <= iter->getLength()) {
            auto level = iter->getLevelAt(pos16);
            if (pos16 == 0) {
                currentLevel = level;
            } else if (level != currentLevel) {
                callback(pos16, start16 - utf16, currentLevel);
                currentLevel = level;
            }
            if (start16 == end16) {
                break;
            }
            SkUnichar u = SkUTF::NextUTF16(&start16, end16);
            pos16 += SkUTF::ToUTF16(u);
        }
    }

    template <typename Callback>
    void forEachBreak(const char16_t utf16[], int utf16Units, SkUnicode::BreakType type, Callback &&callback)
    {
        auto iter = makeBreakIterator(type);
        iter->setText(utf16, utf16Units);
        auto pos = iter->first();
        do {
            callback(pos, iter->status());
            pos = iter->next();
        } while (!iter->isDone());
    }

    virtual void reorderVisual(const BidiLevel runLevels[], int levelsCount, int32_t logicalFromVisual[]) = 0;

    virtual std::unique_ptr<SkUnicode> copy() = 0;

    static std::unique_ptr<SkUnicode> Make();

    static std::unique_ptr<SkUnicode> MakeIcuBasedUnicode();

    static std::unique_ptr<SkUnicode> MakeClientBasedUnicode(SkSpan<char> text, std::vector<SkUnicode::Position> words,
        std::vector<SkUnicode::Position> graphemeBreaks, std::vector<SkUnicode::LineBreakBefore> lineBreaks);

    static std::unique_ptr<SkUnicode> MakeLibgraphemeBasedUnicode();

    static std::unique_ptr<SkUnicode> MakeIcu4xBasedUnicode();
};

namespace sknonstd {
template <> struct is_bitmask_enum<SkUnicode::CodeUnitFlags> : std::true_type {};
} // namespace sknonstd

#endif
// SkUnicode_DEFINED
